﻿#region Copyright
// 
// Copyright (C) 2008 VirtualStaticVoid <virtualstaticvoid@gmail.com>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#endregion

using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;
using NAom.Core;

namespace NAom.DAL
{
  public class EntityRepository<TEntity> : IRepository<TEntity>
    where TEntity : class, IEntity  //, new()
  {

    private readonly DynamicType<TEntity> _dynamicType;
    private readonly IDynamicTypeFactory<TEntity> _dynamicTypeFactory;

    private readonly DataContext _dbCtx;
    private readonly ITable _linq2SQLTable;

    public EntityRepository(EntityType entityType)
    {
      EntityType = entityType;

      _dynamicType = CreateDynamicType(entityType);
      _dynamicTypeFactory = _dynamicType.CreateFactory();

      #region Goo

      // since Type.GetType(string) != _dynamicTypeFactory.GeneratedType for dynamic assemblies,
      //  DataContext.GetTable() fails with InvalidOperationException: Could not retrieve a Table for inheritance subtype 'TDynamicType', try Table of TDynamicType instead. 
      // hook up TypeResolve, so that the correct type can be returned to
      //  the DataContext when it's busy reflecting
      Type generatedType = _dynamicTypeFactory.GeneratedType;

      ResolveEventHandler resolveEventHandler = (o, e) =>
      {
        if (String.CompareOrdinal(generatedType.FullName, e.Name) == 0)
          return generatedType.Assembly;
        return null;
      };

      AppDomain.CurrentDomain.TypeResolve += resolveEventHandler;

      #endregion

      _dbCtx = CreateDataContext(entityType, _dynamicTypeFactory.GeneratedType);
      _linq2SQLTable = GetTable(_dynamicTypeFactory.GeneratedType);

      #region Goo

      // clean up, remove handler
      AppDomain.CurrentDomain.TypeResolve -= resolveEventHandler;

      #endregion

#if DEBUG
      _dbCtx.Log = Console.Out;
#endif

    }

    public EntityType EntityType { get; private set; }

    #region IRepository<TEntity> Members

    public TEntity CreateInstance()
    {
      return _dynamicTypeFactory.CreateInstance();
    }

    public IQueryable<TEntity> GetAll()
    {
      return new InternalEntityQueryable<TEntity>
        (
          _dynamicTypeFactory.GeneratedType,
          _linq2SQLTable.AsQueryable().OfType<TEntity>()
        );
    }

    public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression)
    {
      return GetAll().Where(expression);
    }

    public void Add(TEntity entity)
    {
      _linq2SQLTable.InsertOnSubmit(entity);
      _dbCtx.SubmitChanges();
    }

    public void Update(TEntity entity)
    {
      _linq2SQLTable.DeleteOnSubmit(entity);
      _dbCtx.SubmitChanges();
    }

    public void Delete(TEntity entity)
    {
      _linq2SQLTable.DeleteOnSubmit(entity);
      _dbCtx.SubmitChanges();
    }

    public void Delete(Expression<Func<TEntity, bool>> expression)
    {
      foreach (TEntity entity in GetAll().Where(expression))
      {
        _linq2SQLTable.DeleteOnSubmit(entity);
      }
      _dbCtx.SubmitChanges();
    }

    #endregion

    #region Support

    private static DynamicType<TEntity> CreateDynamicType(EntityType entityType)
    {
      Type genericPropertyTypeType = typeof(PropertyType<>);

      DynamicType<TEntity> dynamicType = new DynamicType<TEntity>(entityType.Name);

      // add required property types
      dynamicType.Properties.AddRange(InterfaceSupport.DerivePropertyTypes(typeof(IEntity)));

      // now add user-defined properties
      foreach (IInternalAttributeType attributeType in entityType.AttributeTypes)
      {
        Type propertyTypeType = genericPropertyTypeType.MakeGenericType(attributeType.RealDataType);
        IPropertyType propertyType = (IPropertyType)Activator.CreateInstance(propertyTypeType, new object[] { attributeType.Name });
        dynamicType.Properties.Add(propertyType);

        // store for later
        attributeType.PropertyType = propertyType;
      }

      return dynamicType;

    }

    private static DataContext CreateDataContext(EntityType entityType, Type generatedType)
    {

      // generate Linq2SQL mapping
      XDocument mappingXmlDoc = CreateXmlMappingSource(entityType, generatedType);

      // load it
      XmlMappingSource mappingSource = XmlMappingSource.FromXml(mappingXmlDoc.ToString());

      // create the data context using this mapping
      DataContext dbCtx = new DataContext(Properties.Settings.Default.AomDBConnectionString, mappingSource);

      return dbCtx;
    }

    private ITable GetTable(Type generatedType)
    {
      // get the table for the generated type
      return _dbCtx.GetTable(generatedType);
    }

    private static XDocument CreateXmlMappingSource(EntityType entityType, Type generatedType)
    {
      // NOTE: 
      //  The Name column is set as the primary key, since scope_identity() doesn't work
      //  for views which have "instead of" triggers

      XElement typeXElement;
      XNamespace xmlns = "http://schemas.microsoft.com/linqtosql/mapping/2007";
      XDocument mappingXmlDoc = new XDocument
        (
        new XElement
          (
            xmlns + "Database",
            new XAttribute("xmlns", xmlns.NamespaceName),
            new XAttribute("Name", "AomDB"),
            new XElement
            (
              xmlns + "Table",
              new XAttribute("Name", entityType.GeneratedViewName),
              typeXElement = new XElement
               (
                 xmlns + "Type",
                 new XAttribute("Name", generatedType.FullName),
                 new XElement
                 (
                    xmlns + "Column",
                    new XAttribute("Name", "Id"),
                    new XAttribute("Member", "Id"),
                    new XAttribute("Storage", "_dynamicId"),
                    new XAttribute("AutoSync", "OnInsert"),
                    new XAttribute("IsDbGenerated", true),
                    new XAttribute("CanBeNull", false),
                    new XAttribute("UpdateCheck", "Never")
                 ),
                 new XElement(xmlns + "Column", new XAttribute("Name", "Version"), new XAttribute("Member", "Version"), new XAttribute("Storage", "_dynamicVersion"), new XAttribute("AutoSync", "Always"), new XAttribute("IsDbGenerated", true), new XAttribute("IsVersion", true), new XAttribute("CanBeNull", false), new XAttribute("UpdateCheck", "Never")),
                 new XElement(xmlns + "Column", new XAttribute("Name", "Name"), new XAttribute("Member", "Name"), new XAttribute("Storage", "_dynamicName"), new XAttribute("IsPrimaryKey", true), new XAttribute("CanBeNull", false), new XAttribute("UpdateCheck", "Never"))
               )
            )
          )
        );

      foreach (IInternalAttributeType attributeType in entityType.AttributeTypes)
      {
        typeXElement.Add
          (
            new XElement
              (
                xmlns + "Column",
                new XAttribute("Name", attributeType.Name),
                new XAttribute("Member", attributeType.Name),
                new XAttribute("Storage", "_dynamic" + attributeType.Name),
                new XAttribute("CanBeNull", !attributeType.RealDataType.IsValueType),
                new XAttribute("UpdateCheck", "Never")
              )
          );
      }

      return mappingXmlDoc;
    }

    #endregion

  }
}
